Ruby 日記 21日目: module_evalの引数とスコープ
次のプログラムを実行するとどうなりますか
code:gold/ex21/main.rb
module A
B = 42
def f
21
end
end
A.module_eval do
def self.f
p B
end
end
B = 15
A.f
選択肢:
例外が発生する
21が表示される
42が表示される
15が表示される
解説:
module_eval(expr, fname = "(eval)", lineno = 1) -> object
module_eval {|mod| ... } -> object
class_eval(expr, fname = "(eval)", lineno = 1) -> object
class_eval {|mod| ... } -> object
モジュールのコンテキストで文字列 expr またはモジュール自身をブロックパラメータとするブロックを 評価してその結果を返します。
モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。 つまり、そのモジュールの定義式の中にあるかのように実行されます。
ただし、ローカル変数は module_eval/class_eval の外側のスコープと共有します。
ふむふむ
文字列が与えられた場合には、定数とクラス変数のスコープは自身のモジュール定義式内と同じスコープになります。 ブロックが与えられた場合には、定数とクラス変数のスコープはブロックの外側のスコープになります。
!!!
そうなんだ!大事じゃん!
今回の問題はブロックが与えられたパターンだ
なので、定数とクラス変数のスコープはブロックの外側のスコープになりますってことで、module_evalのあとに書かれた B = 15 が効いてくるのだな。
この部分の理解、記憶が曖昧だったな〜
例えば
code:undefined_root_const_B.rb
module A
B = 42
def f
21
end
end
A.module_eval do
def self.f
p B
end
end
# B = 15
A.f
としてみると
code:sh
# ruby gold/ex21/undefined_root_const_B.rb
gold/ex21/undefined_root_const_B.rb:11:in `f': uninitialized constant B (NameError)
from gold/ex21/undefined_root_const_B.rb:17:in `<main>'
となる。なるほどね!
code:sh
# ruby gold/ex21/main.rb
15
正解は「15が表示される」だね。
/icons/hr.icon
今回の問題の引数を、ブロックではなく文字列として渡してみると...?
code:gold/ex21/module-eval-string.rb
module A
B = 42
def f
21
end
end
A.module_eval(<<-CODE)
def self.f
p B
end
CODE
B = 15
A.f
定数とクラス変数のスコープは自身のモジュール定義式内と同じスコープになります なので、Aモジュールで定義された定数Bが出力される
code:sh
# ruby gold/ex21/module-eval-string.rb
42
なるほど〜、理解が深まった。